package suncertify.remoting;

import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

import suncertify.application.ApplicationMode;
import suncertify.application.Log;
import suncertify.application.Strings;
import suncertify.application.exceptions.ServerStartException;
import suncertify.application.exceptions.ServerStopException;

/**
 * This class provides the application's server mode functionality. This class is used when the application is started
 * in the {@link ApplicationMode#SERVER} mode. The class is implemented as a singleton pattern which guarantees that
 * only one instance of this class is created.
 * 
 * <p>
 * This class provides:
 * 
 * <ol>
 * <li>One instance of the {@link RemoteContractorsAccessImpl} class which provides the database access. This class is
 * made available to client applications running on remote computer(s) using Java RMI.</li>
 * <li>The ability to start the server.</li>
 * <li>The ability to stop the server.</li>
 * </ol>
 * 
 * 
 */
public final class RmiServer {

    /**
     * Defines the name of the remote interface.
     */
    static final String REMOTE_CONTRACTORS = "RemoteContractors";

    /**
     * Declares the singleton reference to the server.
     */
    private static RmiServer server = null;

    /**
     * See {@link java.rmi.registry.Registry}
     */
    private Registry registry = null;

    /**
     * Declares the interface that will be made available to clients using remoting.
     */
    private RemoteContractorsAccess remoteContractors = null;

    /**
     * Private empty constructor.
     */
    private RmiServer() {

    }

    /**
     * Returns a reference to the only class instance. When this function is called for the first time then one instance
     * of the class is created and saved in a class private member, subsequent calls to this class return the instance
     * that was created by the first call. This function in combination with the private constructor guarantee that only
     * one instance of this class is created in the application.
     * 
     * @return Reference to the only class instance.
     */
    public static RmiServer getInstance() {

	if (server == null) {
	    server = new RmiServer();
	}
	return server;
    }

    /**
     * Makes the database available remotely as follows:
     * 
     * <ol>
     * <li>Creates one instance of the {@link java.rmi.registry.Registry} class using the provided port number.</li>
     * <li>Creates one instance of the {@link RemoteContractorsAccessImpl} class which provides the data access.</li>
     * <li>Binds the {@link RemoteContractorsAccessImpl} object in the registry using the {@link #REMOTE_CONTRACTORS}
     * constant.</li>
     * </ol>
     * 
     * <p>
     * The above steps make the {@link RemoteContractorsAccessImpl} interface available to client applications, client
     * applications can now request a remote reference to the {@link RemoteContractorsAccessImpl} by providing the
     * server name, port number and the {@link #REMOTE_CONTRACTORS} name.
     * 
     * <p>
     * The reference to the remote contractors interface can be retrieved by multiple clients simultaneously, more than
     * one client can ask for this reference and call its functions. Each client function call is executed by RMI in a
     * separate thread.
     * 
     * <p>
     * This function is called when the user clicks the start button in the server application. This function logs the
     * server start in the application's log file.
     * 
     * @param filePath
     *            The path to the database file.
     * @param portNumber
     *            The port number of the server computer.
     * @throws ServerStartException
     *             Thrown if any error occurs, the original exception can be retrieved with {@link Throwable#getCause()}
     */
    public void startRmiServer(String filePath, int portNumber) throws ServerStartException {

	try {
	    this.registry = java.rmi.registry.LocateRegistry.createRegistry(portNumber);

	    this.remoteContractors = new RemoteContractorsAccessImpl(filePath);

	    this.registry.bind(REMOTE_CONTRACTORS, this.remoteContractors);

	    Log.logInfo("Server started successfully with port number: " + Integer.toString(portNumber));

	} catch (Exception ex) {

	    throw new ServerStartException(Strings.MSG_SERVER_START_ERROR, ex);
	}
    }

    /**
     * This function is the opposite of the {@link #startRmiServer(String, int)}. It makes the remote access to the
     * server's database unavailable as follows:
     * <ol>
     * <li>It calls the {@link Registry#unbind(String)} method using the {@link #REMOTE_CONTRACTORS} constant.</li>
     * <li>It calls the {@link UnicastRemoteObject#unexportObject(java.rmi.Remote, boolean)} using the
     * {@link RemoteContractorsAccessImpl} reference and using the registry reference.</li>
     * <li>It calls the {@link RemoteContractorsAccess#closeContractors()}</li>
     * </ol>
     * 
     * <p>
     * If this function is called then all clients are not able to access the server anymore. This function does not
     * wait until all client requests are served.
     * 
     * <p>
     * This function logs the stop server event in the application's log file.
     * 
     * @throws ServerStopException
     *             Thrown if any error occurs in the function, the original exception can be retrieved using
     *             {@link Throwable#getCause()}
     */
    public void stopRmiServer() throws ServerStopException {

	try {
	    // causes all blocked threads to return.
	    this.remoteContractors.closeContractors();

	    try {
		// give all pending threads a chance to return before the RMI connection is disabled.
		Thread.sleep(500);
	    } catch (Exception e) {
	    }

	    this.registry.unbind(REMOTE_CONTRACTORS);

	    UnicastRemoteObject.unexportObject(this.remoteContractors, true);
	    UnicastRemoteObject.unexportObject(this.registry, true);

	    this.remoteContractors = null;
	    this.registry = null;

	    Log.logInfo("Server stopped successfully");

	} catch (Exception ex) {
	    throw new ServerStopException(Strings.MSG_SERVER_STOP_ERROR, ex);
	}
    }
}
